oo::class create Complete {
    superclass Plugin
    variable Language RenamedPadWidget SearchWidgets Limit StartLen \
        From Choices SearchState Limit

    constructor {renamedPadWidget language startLen limit} {
        set Language $language
        set RenamedPadWidget $renamedPadWidget
        lappend SearchWidgets $RenamedPadWidget
        foreach cp [info class instances CodePad] {
            set pw [set [info object namespace $cp]::PadWidget]
            if {"_$pw" != $renamedPadWidget} {
                lappend SearchWidgets _$pw
            }
        }
        set StartLen $startLen
        set From {}
        set Choices {}
        set SearchState $RenamedPadWidget-backward
        set Limit $limit

        $renamedPadWidget tag configure Complete -background #0A2A1B -foreground #F6CEE3

        set PadWidget [string trimleft $RenamedPadWidget _]
        bind $PadWidget <Control-quoteleft> [subst {
            set comObj [self object]
            set oldInsertPosition \[$RenamedPadWidget index insert\]
            \$comObj completeWord \$oldInsertPosition  \[\$comObj getChoiceManually \[$RenamedPadWidget index insert\]\]
            $RenamedPadWidget mark set insert \$oldInsertPosition
        }]
        bind $PadWidget <Tab> [join [list [subst {
            set ids \[$RenamedPadWidget tag ranges Complete\]
            if {\$ids != {}} {
                [self] clearCompleteMode
                $PadWidget mark set insert \[lindex \$ids end\]
            }
        }] [bind $PadWidget <Tab>]] \n]
        bind $PadWidget <Control-Return> [subst {
            [self object] getChoicesPopup \[$RenamedPadWidget index insert\]
            return -code break
        }]
        bind $PadWidget <Escape> [list + [self] deleteCompletePart]
    }

    method genPopup {choices} {
        toplevel .complete -bd 0
        set choicesLen [llength $choices]
        if {$choicesLen > $Limit} {
            scrollbar .complete.choicesYScroll -command ".complete.choices yview"
            listbox .complete.choices -height $Limit \
                -yscroll ".complete.choicesYScroll set"
        } else {
            listbox .complete.choices -height $choicesLen
        }
        .complete.choices configure -bd 0 -activestyle none \
            -selectmode browse -selectbackground #FFFFFF \
            -selectforeground #B45F04 \
            -font [lindex [$RenamedPadWidget configure -font] end]

        set width 0
        foreach c $choices {
            set wd [string length $c]
            if {$wd > $width} {set width $wd}
            .complete.choices insert end $c
        }
        .complete.choices configure -width [expr $width+3]
        pack .complete.choices
        focus .complete.choices
        .complete.choices selection set 0

        wm overrideredirect .complete 1
        wm withdraw .complete

        bind .complete.choices <Return> [subst {
            set obj [self object]
            \$obj completeWord \[$RenamedPadWidget index insert\] \
                \[.complete.choices get active\]
            \$obj destroyPopup
            \$obj clearCompleteMode
        }]

        bind .complete.choices <Escape> [subst {
            [self object] destroyPopup
        }]

        bind .complete.choices <Button-1> [join [list [list set obj [self object]] {
            set rootx [winfo rootx .complete]
            set rooty [winfo rooty .complete]
            set width [winfo reqwidth .complete]
            set height [winfo reqheight .complete]
            if {%X < $rootx || %X > $rootx+$width || %Y < $rooty || %Y > $rooty+$height} {
                $obj destroyPopup
            } else {
                $obj completeWord insert \
                    [.complete.choices get @%x,%y]
                $obj destroyPopup
                $obj clearCompleteMode
            }
        }] \n]
    }

    method destroyPopup {} {
        after idle {
            destroy .complete
        }
    }

    method showPopup {index} {
        set PadWidget [string trimleft $RenamedPadWidget _]
        set rootx [winfo rootx $PadWidget]
        set rooty [winfo rooty $PadWidget]
        foreach {x y w h} [$RenamedPadWidget bbox $index] {
            set x [expr $rootx+$x]
            set y [expr $rooty+$y+$h]
        }
        set popupWidth [winfo reqwidth .complete.choices]
        set popupHeight [winfo reqheight .complete.choices]
        set screenWidth [winfo screenwidth .]
        set screenHeight [winfo screenheight .]

        if {$y+$popupHeight > $screenHeight} {
            set y [expr $y-$popupHeight-$h]
        }
        set wGap [expr $x+$popupWidth > $screenWidth]
        if {$wGap > 0} {
            set x [expr $x - $wGap]
        }

        wm geometry .complete +$x+$y
        my fadeInPopup 0.0
        wm deiconify .complete
        grab .complete.choices
        tkwait visibility .complete
    }

    method fadeInPopup {percentage} {
        if [winfo exists .complete] {
            wm attribute .complete -alpha $percentage
            if {$percentage < 1.0} {
                after 50 [list [self object] fadeInPopup [expr $percentage + 0.2]]
            }
        }
    }

    method appendSearchWidget {w} {
        foreach s $SearchWidgets {
            if {$s == "_$w"} { return }
        }
        lappend SearchWidgets _$w
    }

    method removeSearchWidget {w} {
        set id [lsearch $SearchWidgets $w]
        if {$id >= 0} {
            set SearchWidgets [lreplace $SearchWidgets $id $id]
        }
    }

    method isInCompleteMode? {} {
        return [expr [llength [$RenamedPadWidget tag range Complete]] != 0]
    }

    method clearCompleteMode {args} {
        $RenamedPadWidget tag remove Complete 1.0 end
        set From {}
        set Choices {}
        set SearchState $RenamedPadWidget-backward
    }
    
    method deleteCompletePart {args} {
        if {[$RenamedPadWidget tag ranges Complete] != {}} {
            $RenamedPadWidget delete Complete.first Complete.last
        }
        my clearCompleteMode
    }

    method findCompleteChoice {index widget from {dir forward}} {
        if [my isInCompleteMode?] {
            set index [lindex [$RenamedPadWidget tag range Complete] 0]
        }

        set prefix [$RenamedPadWidget get [$Language getWordHeadIndex $RenamedPadWidget $index] $index]
        if {$dir == {backward}} {
            set lineStart [$widget index "$from linestart"]
            set content [$widget get $lineStart $from]
        } else {
            if {$widget == $RenamedPadWidget} {
                set lineStart [$Language getWordEndIndex $widget $from]
            } else {
                set lineStart $from
            }
            set content [$widget get $lineStart "$from lineend"]
        }

        while {![info exists preStart] || int($preStart) != int($lineStart)} {
            foreach w [regexp -inline -all {[\w:]+} $content] {
                if {![string first $prefix $w] && $w != $prefix} {
                    # SET STATE: FROM
                    if {[lsearch $Choices $w] == -1} {
                        set From [$widget index $lineStart+[string first $w $content]c]
                        lappend Choices $w
                        return $w
                    }
                }
            }
            set preStart $lineStart
            if {$dir == {backward}} {
                set lineStart [$widget index "$lineStart -1l linestart"]
            } else {
                set lineStart [$widget index "$lineStart +1l linestart"]
            }
            set content [$widget get $lineStart "$lineStart lineend"]
        }
    }

    method findNextChoice {index} {
        foreach {widget dir} [split $SearchState -] {}
        set choice [my findCompleteChoice $index $widget $From $dir]
        if {$choice == {}} {
            # SET STATES
            if {$dir == "backward"} {
                set SearchState $widget-forward
            } else {
                set id [lsearch $SearchWidgets $widget]
                # puts "$id -> $SearchWidgets"
                if {$id == [llength $SearchWidgets]-1} {
                    return
                }
                set nextWidget [lindex $SearchWidgets $id+1]
                set SearchState $nextWidget-forward
                # puts $SearchState
                set From 1.0
            }
            tailcall my findNextChoice $index
        } else {
            return $choice
        }
    }

    method completeWord {index word} {
        set len [string length \
                     [$RenamedPadWidget get [$Language getWordHeadIndex $RenamedPadWidget $index] $index]]
        set completePart [string range $word $len end]
        # set wordEnd [$Language getWordEndIndex $RenamedPadWidget $index]
        set completeEnd [lindex [$RenamedPadWidget tag nextrange Complete $index] 1]
        set padWidget [string trimleft $RenamedPadWidget]
        if {$completeEnd == {}} {
            $padWidget insert $index $completePart
        } else {
            $padWidget replace $index $completeEnd $completePart
        }
        $RenamedPadWidget tag add Complete $index $index+[string length $completePart]c
    }

    method getChoiceAfterInsert {index content args} {
        set i 0; set ids [$RenamedPadWidget tag range Complete]
        while {$i < [llength $ids]-1} {
            $RenamedPadWidget delete [lindex $ids $i] [lindex $ids $i+1]
            incr i 2
        }

        set index [$RenamedPadWidget index $index+[string length $content]c]
        set worStart [$Language getWordHeadIndex $RenamedPadWidget $index]
        set prefix [$RenamedPadWidget get $worStart $index]
        if {[string length $prefix] < $StartLen} { return }

        my clearCompleteMode
        set From $worStart
        if {[set choice [my findNextChoice $index]] != {}} {
            my completeWord $index $choice
            $RenamedPadWidget mark set insert $index
        }
    }

    method getChoiceManually {index} {
        if {$From == {}} {set From [$RenamedPadWidget index insert]}
        # puts $Choices
        set choice [my findNextChoice $index]
        if {$choice == {}} {
            set choice [lindex $Choices 0]
            set Choices [lreplace $Choices 0 0]
            lappend Choices $choice
        }
        return $choice
    }

    method getChoicesPopup {index} {
        set i 0; set ids [$RenamedPadWidget tag range Complete]
        while {$i < [llength $ids]-1} {
            $RenamedPadWidget delete [lindex $ids $i] [lindex $ids $i+1]
            incr i 2
        }
        set worStart [$Language getWordHeadIndex $RenamedPadWidget $index]
        set prefix [$RenamedPadWidget get $worStart $index]
        if ![regexp {[\w:]+} $prefix] { return }
        my clearCompleteMode
        set From $worStart

        set i 0
        while {$i < $Limit} {
            if {[set c [my findNextChoice $index]] == {}} {break}
            incr i
        }
        if {$Choices != {}} {
            my genPopup $Choices
            my showPopup $worStart
        }
    }

    method getPluginEvents {} {
        return {
            {afterInsert getChoiceAfterInsert}
            {afterMarkSetInsert deleteCompletePart}
        }
    }
}